#ifndef __JPG_H__
#define __JPG_H__

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include "jpeglib.h"

// 自定义错误管理器，用于捕获解码过程中的错误
struct my_error_mgr
{
    struct jpeg_error_mgr pub; // libjpeg的公共错误结构体
    jmp_buf setjmp_buffer;     // 错误跳转缓冲区
};
typedef struct my_error_mgr *my_error_ptr;

// 错误处理回调函数，替代默认的exit行为
METHODDEF(void)
my_error_exit(j_common_ptr cinfo)
{
    // 将错误指针转换为自定义错误管理器类型
    my_error_ptr myerr = (my_error_ptr)cinfo->err;

    // 打印libjpeg原生错误信息
    (*cinfo->err->output_message)(cinfo);

    // 跳回setjmp设置的位置，携带错误标记
    longjmp(myerr->setjmp_buffer, 1);
}

/**
 * @brief 解码JPEG文件为RGB数据
 * @param filename 输入JPEG文件路径
 * @param width 输出图片宽度（指针，用于返回实际宽度）
 * @param height 输出图片高度（指针，用于返回实际高度）
 * @return 分配的RGB数据缓冲区（每个像素3字节：R、G、B），失败返回NULL
 */
unsigned char *decode_jpeg(const char *filename, int *width, int *height)
{
    // 初始化JPEG解码结构体
    struct jpeg_decompress_struct cinfo;
    struct my_error_mgr jerr;

    FILE *infile;            // 输入文件指针
    JSAMPARRAY buffer;       // 一行像素数据的缓冲区
    int row_stride;          // 一行像素的字节数
    unsigned char *rgb_data; // 存储最终的RGB数据
    unsigned char *ptr;      // 用于填充RGB数据的指针

    // 打开JPEG文件
    if ((infile = fopen(filename, "rb")) == NULL)
    {
        fprintf(stderr, "无法打开文件: %s\n", filename);
        return NULL;
    }

    // 配置错误处理
    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = my_error_exit;

    // 设置错误跳转点：如果解码过程出错，会跳回这里
    if (setjmp(jerr.setjmp_buffer))
    {
        jpeg_destroy_decompress(&cinfo);
        fclose(infile);
        fprintf(stderr, "JPEG解码失败\n");
        return NULL;
    }

    // 初始化JPEG解码器
    jpeg_create_decompress(&cinfo);

    // 指定输入文件
    jpeg_stdio_src(&cinfo, infile);

    // 读取JPEG文件头，获取图片信息
    jpeg_read_header(&cinfo, TRUE);

    // 开始解码
    jpeg_start_decompress(&cinfo);

    // 获取图片宽度和高度
    *width = cinfo.output_width;
    *height = cinfo.output_height;

    // 计算一行像素的字节数（宽度 × 3通道）
    row_stride = cinfo.output_width * cinfo.output_components;

    // 分配存储RGB数据的缓冲区
    rgb_data = (unsigned char *)malloc(*width * *height * 3);
    if (rgb_data == NULL)
    {
        fprintf(stderr, "内存分配失败\n");
        jpeg_destroy_decompress(&cinfo);
        fclose(infile);
        return NULL;
    }
    ptr = rgb_data;

    // 分配一行像素的缓冲区
    buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);

    // 逐行读取解码后的数据并存储到RGB缓冲区
    while (cinfo.output_scanline < cinfo.output_height)
    {
        // 读取一行像素数据
        jpeg_read_scanlines(&cinfo, buffer, 1);

        // 将一行数据复制到RGB缓冲区
        memcpy(ptr, buffer[0], row_stride);
        ptr += row_stride;
    }

    // 完成解码
    jpeg_finish_decompress(&cinfo);

    // 释放解码器资源
    jpeg_destroy_decompress(&cinfo);

    // 关闭文件
    fclose(infile);

    printf("JPEG解码成功: %s\n", filename);
    printf("图片尺寸: %dx%d 像素\n", *width, *height);
    return rgb_data;
}

// -------------------------- 1. 自定义错误管理器（关键：避免程序崩溃） --------------------------
// 继承 libjpeg 的公共错误结构体，添加跳转缓冲区
struct my_jpeg_error_mgr
{
    struct jpeg_error_mgr pub; // libjpeg 默认错误字段
    jmp_buf setjmp_buffer;     // 错误时跳转的缓冲区
};
typedef struct my_jpeg_error_mgr *my_jpeg_error_ptr;

// 错误回调函数：替代 libjpeg 默认的 "直接 exit" 行为
METHODDEF(void)
my_jpeg_error_exit(j_common_ptr cinfo)
{
    // 将 libjpeg 错误指针转为自定义类型
    my_jpeg_error_ptr my_err = (my_jpeg_error_ptr)cinfo->err;

    // 打印 libjpeg 原生错误信息（如 "Invalid quality"）
    (*cinfo->err->output_message)(cinfo);

    // 跳回 setjmp 位置，携带错误标记（1 表示错误）
    longjmp(my_err->setjmp_buffer, 1);
}

// -------------------------- 2. RGB 转 JPG 核心函数 --------------------------
/**
 * @brief 生成 JPG 图片
 * @param rgb_data      输入 RGB 数据（每个像素 3 字节：R→G→B）
 * @param width         图片宽度（像素）
 * @param height        图片高度（像素）
 * @param jpg_path      输出 JPG 文件路径（如 "output.jpg"）
 * @param quality       压缩质量（0-100，100=无损，建议 70-90）
 * @return 1=成功，0=失败
 */
int generate_jpg(const unsigned char *rgb_data, int width, int height,
                 const char *jpg_path, int quality)
{
    // ------------ 步骤 1：校验输入参数 ------------
    if (rgb_data == NULL || width <= 0 || height <= 0 || jpg_path == NULL)
    {
        fprintf(stderr, "错误：无效输入参数（RGB数据为空或尺寸非法）\n");
        return 0;
    }
    // 修正非法质量值（0-100 范围）
    if (quality < 0)
        quality = 0;
    if (quality > 100)
        quality = 100;

    // ------------ 步骤 2：初始化 JPEG 编码器和错误处理 ------------
    struct jpeg_compress_struct cinfo; // 编码器上下文
    struct my_jpeg_error_mgr jerr;     // 自定义错误管理器

    // 绑定错误处理：必须在初始化编码器前设置
    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = my_jpeg_error_exit; // 错误时调用自定义回调

    // 设置错误跳转点：若后续编码出错，会跳回这里
    if (setjmp(jerr.setjmp_buffer))
    {
        // 错误处理：释放已分配的编码器资源
        jpeg_destroy_compress(&cinfo);
        return 0;
    }

    // ------------ 步骤 3：初始化编码器 ------------
    jpeg_create_compress(&cinfo); // 分配编码器内存

    // ------------ 步骤 4：打开输出文件（二进制模式！） ------------
    FILE *jpg_file = fopen(jpg_path, "wb"); // "wb" 避免 Windows 换行符转换损坏文件
    if (jpg_file == NULL)
    {
        fprintf(stderr, "错误：创建文件 [%s] 失败：%s\n", jpg_path, strerror(errno));
        jpeg_destroy_compress(&cinfo); // 释放编码器
        return 0;
    }
    // 将文件绑定到编码器输出（后续编码数据直接写入文件）
    jpeg_stdio_dest(&cinfo, jpg_file);

    // ------------ 步骤 5：配置编码核心参数 ------------
    cinfo.image_width = width;      // 图片宽度（像素）
    cinfo.image_height = height;    // 图片高度（像素）
    cinfo.input_components = 3;     // 输入通道数（RGB 为 3）
    cinfo.in_color_space = JCS_RGB; // 输入色彩空间（RGB）

    // ------------ 步骤 6：设置默认参数 + 压缩质量 ------------
    jpeg_set_defaults(&cinfo); // 加载 libjpeg 默认配置（霍夫曼表、量化表）
    // 设置压缩质量：第二个参数 TRUE 表示启用优化（压缩效率更高）
    jpeg_set_quality(&cinfo, quality, TRUE);

    // ------------ 步骤 7：开始编码（初始化编码状态） ------------
    // 第二个参数 TRUE 表示 "生成完整 JPG 文件"（含文件头、图像数据、结束标记）
    jpeg_start_compress(&cinfo, TRUE);

    // ------------ 步骤 8：逐行写入 RGB 数据（libjpeg 要求按行处理） ------------
    int row_stride = width * 3; // 每行字节数（宽度 × 3 通道）
    JSAMPROW row_ptr[1];        // 存储一行像素的指针（libjpeg 要求数组形式）

    // 循环编码每一行：直到所有行处理完成
    while (cinfo.next_scanline < cinfo.image_height)
    {
        // 计算当前行在 RGB 数据中的起始地址
        row_ptr[0] = (unsigned char *)rgb_data + cinfo.next_scanline * row_stride;
        // 写入一行数据并编码（第三个参数：本次写入 1 行）
        jpeg_write_scanlines(&cinfo, row_ptr, 1);
    }

    // ------------ 步骤 9：完成编码 + 清理资源 ------------
    jpeg_finish_compress(&cinfo);  // 完成剩余数据编码，刷新文件缓冲区
    jpeg_destroy_compress(&cinfo); // 释放编码器所有内存
    fclose(jpg_file);              // 关闭文件（必须在 jpeg_finish_compress 之后）

    printf("成功生成 JPG 文件：%s\n", jpg_path);
    printf("图片信息：%dx%d 像素，压缩质量：%d\n", width, height, quality);
    return 1;
}



#endif